package com.zpz.framework.zpzoauth.config.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.zpz.framework.zpzoauth.common.encrypt.DefaultCrypto;
import com.zpz.framework.zpzoauth.common.utils.ZpzOauthCommonConstant;
import com.zpz.framework.zpzoauth.common.utils.ZpzOauthRequestUtil;
import com.zpz.framework.zpzoauth.config.xss.XssAndSqlHttpServletRequestWrapper;
import com.zpz.framework.zpzoauth.pojo.po.OauthClientDetails;
import com.zpz.framework.zpzoauth.pojo.vo.GetAccessByRoleVo;
import com.zpz.framework.zpzoauth.service.ZpzOauthPermissionService;
import eu.bitwalker.useragentutils.UserAgent;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.util.StreamUtils;

import javax.annotation.Resource;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.util.*;

/**
 * @author zhangpanzhi
 * @time 2019-07-09
 * @description 未经本人允许请勿随便改动，尊重劳动
 */
@Configuration
@Order(1)
public class SecurityFilter implements Filter {

    @Value("${zpzOauth.encrypt.requenable}")
    private Boolean requenable;
    @Value("${zpzOauth.antiattack.xssandsqlenable}")
    private Boolean xssandsqlenable;
    @Value("${zpzOauth.burglarChain.enable}")
    private Boolean enable;
    @Value("${server.servlet.context-path}")
    private String controllerPre;
    private Logger log = LoggerFactory.getLogger(SecurityFilter.class);
    @Resource
    private ZpzOauthPermissionService permissionService;
    @Resource
    private ZpzOauthRequestUtil zpzOauthRequestUtil;
    @Bean
    public FilterRegistrationBean dataSecurityBean() {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        SecurityFilter filter = new SecurityFilter();
        List<String> urlPatterns = new ArrayList<String>();
        urlPatterns.add("/*");
        registrationBean.setUrlPatterns(urlPatterns);
        registrationBean.setFilter(filter);
        return registrationBean;
    }
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    private Map<String,Object> dataSecurity(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException{
        // 判断是否需要数据安全校验
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
//        boolean flag = true;
        String msg ="";
        if (requenable) {
            String header = request.getHeader("content-type");
            if (StringUtils.isNotBlank(header) && (header.startsWith("application/json") || header.startsWith("application/x-www-form-urlencoded"))) {
                //只对application/json做加解密签名处理
                String lience = request.getHeader(ZpzOauthCommonConstant.REQUEST_HEADER_LIENCE);
                String clientId = null;
                String apicode = null;
                OauthClientDetails client = null;
                byte[] reqBodyBytes = null;
                UserAgent userAgent = request != null ? UserAgent.parseUserAgentString(request.getHeader("User-Agent")) : null;
                String ipAddress = ZpzOauthRequestUtil.getIpAddress(request);
                log.info("业务请求[" + apicode + "]：::::::::::请求设备信息(IP::" + ipAddress + ")：" + JSON.toJSONString(userAgent));
                try {
                    if (StringUtils.isNotBlank(lience)) {
                        String _license = new String(Base64.decodeBase64(lience));
                        log.info("业务请求[" + apicode + "]：:：：：：：权限认证：：：获取的头部license  base64解完码后：：：：：：" + JSON.toJSONString(_license));
                        String[] liencese = _license.split(":");
                        clientId = liencese[0];
                        apicode = liencese[1];
                        log.info("业务请求[" + apicode + "]：:：：：：权限认证：：：获取的头部license  base64解完码分割完数组：：：：：：" + JSON.toJSONString(liencese));
                    }
                } catch (Exception e) {
                    msg = "业务请求[" + apicode + "]：:：：：：权限认证：：：获取的头部license  base64解完码分割异常：：：：：：" + e.getMessage();
                    return getResult(request,response,false,msg);
                }
                /**
                 * Body参数请求
                 * */
                if (header.startsWith("application/json")) {
                    //需要登录的接口才能够进行加解密验签
                    List<GetAccessByRoleVo> accessByRoleVos = null;
                    try {
                        // 获取所有的权限
                        accessByRoleVos = permissionService.getAccessByClient();
                    } catch (Exception e) {
                        msg = "无效的请求，查无此接口";
                        return getResult(request,response,false,msg);
                    }
//                        匹配路径 判断是否需要加解密签名
                    if (accessByRoleVos == null) {
                        msg = "无效的请求，查无此接口";
                        return getResult(request,response,false,msg);
                    }
                    String set_de = "1";
                    String set_check_sign = "1";
                    String requestURI = request.getRequestURI();
                    for (GetAccessByRoleVo v : accessByRoleVos) {
                        if (v.getMethodUrl().equals(requestURI)) {
                            set_de = v.getSetDe() + "";
                            set_check_sign = v.getSetCheckSign() + "";
                        }
                    }
                    if (set_check_sign.equals("2") && set_de.equals("2")) {
                        return getResult(request,response,true,msg);
                    }
                    if (!StringUtils.isNotBlank(lience)) {
                        msg = "业务请求[" + apicode + "]：:：：：：权限认证：：：license为空：：：：：：";
                        return getResult(request,response,false,msg);
                    }
                    //请求头必须携带clientId
                    if (!StringUtils.isNotBlank(clientId)) {
                        msg = "业务请求[" + apicode + "]：:：：：：权限认证：：：license 中clientId为空：：：：：：";
                        return getResult(request,response,false,msg);
                    }
                    //请求头必须携带apicode
                    if (!StringUtils.isNotBlank(apicode)) {
                        msg = "业务请求[" + apicode + "]：:：：：：权限认证：：：license 中apicode为空：：：：：：";
                        return getResult(request,response,false,msg);
                    }
                    // 解析数据
                    ServletInputStream servletInputStream = request.getInputStream();
                    String data = null; // 请求体加密数据
                    String decryptedDataStr = null; // 解过密的字符串
                    Map<String, Object> param = null;// 明文解密数据
                    String noncestr = null;
                    String timestamp = null;
                    if (servletInputStream != null) {
                        try {
                            data = StreamUtils.copyToString(servletInputStream, Charset.forName("utf-8"));
                        } catch (IOException e) {
                            msg = "业务请求[" + apicode + "]：：：：：请求数据获取失败：：：：：：" + e.getMessage();
                            return getResult(request,response,false,msg);
                        }
                    }
                    if (ZpzOauthCommonConstant.SET_DE_VALUE.equals(set_de)) {
                        //判断是否需要解密
                        if (client == null) {
                            client = zpzOauthRequestUtil.getClient(clientId);
                            if (client == null){
                                msg = "业务请求[" + apicode + "]：：：：：：：：：无效的客户端：：：：：：" + data;
                                return getResult(request,response,false,msg);
                            }
                        }
                        //数据解密
                        if (StringUtils.isNotBlank(data)) {
                            try {
                                Map<String, Object> re = (Map<String, Object>) JSON.parse(data);
                                if (re == null) {
                                    msg = "业务请求[" + apicode + "]：：：：：：：：：请求数据转型失败：：：：：：" + data;
                                    return getResult(request,response,false,msg);
                                }
                                Object dare = re.get("data");
                                Object noncestrre = re.get("noncestr");
                                Object timestampre = re.get("timestamp");
                                if (dare == null || dare.toString().equals("")) {
                                    msg = "业务请求[" + apicode + "]：：：：：：：：：数据解密失败::加密数据为空：：：：：：" + data;
                                    return getResult(request,response,false,msg);
                                }
                                data = dare.toString();
                                log.info("业务请求[" + apicode + "]：：：：：：：：：加密数据：：：：：：" + data);
                                decryptedDataStr = DefaultCrypto.decodDataPrivateKey(data, client.getPrivateKey());
                                log.info("业务请求[" + apicode + "]：：：：：：：：：解密数据：：：：：：" + decryptedDataStr);
                                decryptedDataStr = new String(Base64.decodeBase64(decryptedDataStr.getBytes("UTF-8")), "UTF-8");
                                noncestr = noncestrre != null ? noncestrre.toString() : null;
                                timestamp = timestampre != null ? timestampre.toString() : null;
                                param = (Map<String, Object>) JSON.parseObject(decryptedDataStr, Feature.OrderedField);
                            } catch (Exception e) {
                                msg = "业务请求[" + apicode + "]：：：：：：：：：数据解密失败：：：：：：" + e.getMessage();
                                return getResult(request,response,false,msg);
                            }
                            if (!StringUtils.isNotBlank(decryptedDataStr)) {
                                msg = "业务请求[" + apicode + "]：：：：：：：：：数据解密失败-解密结果为空：：：：：：";
                                return getResult(request,response,false,msg);
                            }
                            reqBodyBytes = decryptedDataStr.getBytes("UTF-8");
                            request = modifyBodyRequest(reqBodyBytes, request);
                        }
                    } else {
                        if (StringUtils.isNotBlank(data)) {
                            param = (Map<String, Object>) JSON.parse(data);//包括了timestamp/noncestr
                            if (param != null && param.size() > 0) {
                                if (param.get("noncestr") != null) {
                                    noncestr = param.get("noncestr").toString();
                                    param.remove("noncestr");
                                }
                                if (param.get("timestamp") != null) {
                                    timestamp = param.get("timestamp").toString();
                                    param.remove("timestamp");
                                }
                                reqBodyBytes = JSON.toJSONString(param).getBytes("UTF-8");
                            } else {
                                reqBodyBytes = "".getBytes("UTF-8");
                            }
                        } else {
                            reqBodyBytes = "".getBytes("UTF-8");
                        }
                        request = modifyBodyRequest(reqBodyBytes, request);

                    }
                    //判断是否需要签名
                    if (ZpzOauthCommonConstant.SET_CHECK_SIGN_VALUE.equals(set_check_sign)) {
                        if (client == null) {
                            client = zpzOauthRequestUtil.getClient(clientId);
                            if (client == null){
                                msg = "业务请求[" + apicode + "]：：：：：：：：：无效的客户端：：：：：：" + data;
                                return getResult(request,response,false,msg);
                            }
                        }
                        //判断是否需要签名
                        String sign = null;
                        try {
                            sign = request.getHeader(ZpzOauthCommonConstant.X_CA_PROXY_SIGNATURE);
                        } catch (Exception e) {
                            msg = "业务请求[" + apicode + "]：：：：：：：：：请求头部签名获取失败：：：：：：" + e.getMessage();
                            return getResult(request,response,false,msg);
                        }
                        if (!StringUtils.isNotBlank(sign)) {
                            msg = "业务请求[" + apicode + "]：：：：：：：：：请求头部签名为空：：：：：：";
                            return getResult(request,response,false,msg);
                        }
                        Map<String, String> map = coverMap(param);
                        boolean signatureValid = false;
                        try {
                            signatureValid = DefaultCrypto.signatureValid(map, noncestr, timestamp, client.getSignKey(), sign);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        if (!signatureValid) {
                            msg = "业务请求[" + apicode + "]：：：：：：：：：验签失败：：：：：：";
                            return getResult(request,response,false,msg);
                        }
                    }
                } else if (header.startsWith("application/x-www-form-urlencoded")) {
                    if (request.getRequestURI().equals(String.format("%s/oauth/token",controllerPre))) {
                        if (client == null) {
                            client = zpzOauthRequestUtil.getClient(clientId);
                            if (client == null){
                                msg = "业务请求[" + apicode + "]：：：：：：：：：无效的客户端：：：：：：" ;
                                return getResult(request,response,false,msg);
                            }
                        }
                        Map<String, String[]> parameterMap = request.getParameterMap();
                        String[] datas = parameterMap.get("data");
                        String[] noncestrs = parameterMap.get("noncestr");
                        String[] timestamps = parameterMap.get("timestamp");
                        log.info(String.format("数据Data-----%s-----随机串------%s------时间戳-------%s",JSON.toJSONString(datas),JSON.toJSONString(noncestrs),JSON.toJSONString(timestamps)));
                        if (datas == null || datas.length == 0 || noncestrs == null || noncestrs.length == 0 || timestamps == null || timestamps.length == 0) {
                            msg = "登录必须进行参数加密与签名[" + apicode + "]：：：：：：：：：验签失败：：：：：：";
                            return getResult(request,response,false,msg);
                        }
                        String decryptedDataStr = null;
                        Map<String, Object> parma = null;
                        try {
                            decryptedDataStr = DefaultCrypto.decodDataPrivateKey(datas[0], client.getPrivateKey());
                            log.info("业务请求[" + apicode + "]：：：：：：：：：解密数据：：：：：：" + decryptedDataStr);
                            decryptedDataStr = new String(Base64.decodeBase64(decryptedDataStr.getBytes("UTF-8")), "UTF-8");
                            parma = (Map<String, Object>) JSON.parseObject(decryptedDataStr, Feature.OrderedField);
                            log.info(String.format("-------数据（明）-----%s",JSON.toJSONString(parma)));
                        } catch (Exception e) {
                            msg = "解密失败[" + apicode + "]：：：：：：：：：：：：：：";
                            log.error(e.getMessage(),e);
                            return getResult(request,response,false,msg);
                        }
                        if (!StringUtils.isNotBlank(decryptedDataStr)) {
                            msg = "业务请求[" + apicode + "]：：：：：：：：：数据解密失败-解密结果为空：：：：：：";
                            return getResult(request,response,false,msg);
                        }
                        //判断是否需要签名
                        String sign = null;
                        try {
                            sign = request.getHeader(ZpzOauthCommonConstant.X_CA_PROXY_SIGNATURE);
                        } catch (Exception e) {
                            msg = "业务请求[" + apicode + "]：：：：：：：：：请求头部签名获取失败：：：：：：" + e.getMessage();
                            return getResult(request,response,false,msg);
                        }
                        if (!StringUtils.isNotBlank(sign)) {
                            msg = "业务请求[" + apicode + "]：：：：：：：：：请求头部签名为空：：：：：：";
                            return getResult(request,response,false,msg);
                        }
                        Map<String, String> map = coverMap(parma);
                        boolean signatureValid = false;
                        try {
                            signatureValid = DefaultCrypto.signatureValid(map, noncestrs[0], timestamps[0], client.getSignKey(), sign);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        if (!signatureValid) {
                            msg = "业务请求[" + apicode + "]：：：：：：：：：验签失败：：：：：：";
                            return getResult(request,response,false,msg);
                        }
                        Map<String, String[]> parmap = new HashMap<>();
                        for (String st : parma.keySet()) {
                            parmap.put(st, new String[]{parma.get(st).toString()});
                        }
                        request = modifyParameterRequest(parmap, request);
                    }
                }
            }
        }
        return getResult(request,response,true,"");
    }
    private Map<String,Object> getResult(HttpServletRequest request,HttpServletResponse response,Boolean flag,String msg){
        Map<String,Object> re=new HashMap<>();
        re.put("request",request);
        re.put("response",response);
        re.put("flag",flag);
        re.put("msg",msg);
        return re;
    }
    // 获取request请求body中参数
    public static String getBodyString(BufferedReader br) {
        String inputLine;
        String str = "";
        try {
            while ((inputLine = br.readLine()) != null) {
                str += inputLine;
            }
            br.close();
        } catch (IOException e) {
            System.out.println("IOException: " + e);
        }
        return str;

    }
    private Map<String,Object> xssAndSql(HttpServletResponse response,HttpServletRequest request) throws IOException{
        String msg="";
        if (xssandsqlenable){
            String method = "GET";
            String param = "";
            XssAndSqlHttpServletRequestWrapper xssRequest = null;
            if (request instanceof HttpServletRequest) {
                method = request.getMethod();
                xssRequest = new XssAndSqlHttpServletRequestWrapper(request);
                request = xssRequest;
            }
            if ("POST".equalsIgnoreCase(method)) {
                param = SecurityFilter.getBodyString(xssRequest.getReader());
                if(StringUtils.isNotBlank(param)){
                    if(xssRequest.checkXSSAndSql(param)){
                        msg="非法的参数，拒绝访问!";
                        return getResult(request,response,false,msg);
                    }
                }
            }
            if (xssRequest.checkParameter()) {
                msg="非法的参数，拒绝访问!";
                return getResult(request,response,false,msg);
            }
        }
        return getResult(request,response,true,msg);
    }
    private Map<String,Object> burglar(ServletRequest req, ServletResponse resp){
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        String msg="";
        if (enable){
            String referrer = request.getHeader("referer");
            String basePath = request.getScheme() + "://" + request.getServerName();
            if (referrer == null || referrer.equals("") || !referrer.startsWith(basePath)) {
                msg="非法的请求,拒绝访问";
                return getResult(request,response,false,msg);
            }
        }
        return getResult(request,response,true,msg);
    }
    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        // 防盗链
        Map<String, Object> map = burglar(req,resp);
        Boolean flag = (Boolean) map.get("flag");
        HttpServletRequest request = (HttpServletRequest) map.get("request");
        HttpServletResponse response = (HttpServletResponse) map.get("response");
        if (!flag){
            req.setAttribute("error", map.get("msg"));
            req.getRequestDispatcher("/error/exthrow").forward(request, response);
            return;
        }
        // 判断是否需要数据安全校验
        map = dataSecurity(req, resp, chain);
        flag = (Boolean) map.get("flag");
        request = (HttpServletRequest) map.get("request");
        response = (HttpServletResponse) map.get("response");
        if (!flag){
            req.setAttribute("error", map.get("msg"));
            req.getRequestDispatcher("/error/exthrow").forward(request, response);
            return;
        }
        map = xssAndSql(response,request);
        flag = (Boolean) map.get("flag");
        request = (HttpServletRequest) map.get("request");
        response = (HttpServletResponse) map.get("response");
        if (!flag){
            req.setAttribute("error", map.get("msg"));
            req.getRequestDispatcher("/error/exthrow").forward(request, response);
            return;
        }
        chain.doFilter(request, response);
    }


    @Override
    public void destroy() {
    }

    private HttpServletRequestWrapper modifyParameterRequest(Map<String, String[]> parameterMap, HttpServletRequest request) {
        HttpServletRequestWrapper httpServletRequestWrapper = new HttpServletRequestWrapper(request) {
            // 重写几个HttpServletRequestWrapper中的方法

            /**
             * 获取所有参数名
             *
             * @return 返回所有参数名
             */
            @Override
            public Enumeration<String> getParameterNames() {
                Vector<String> vector = new Vector<String>(parameterMap.keySet());
                return vector.elements();
            }

            /**
             * 获取指定参数名的值，如果有重复的参数名，则返回第一个的值 接收一般变量 ，如text类型
             *
             * @param name 指定参数名
             * @return 指定参数名的值
             */
            @Override
            public String getParameter(String name) {
                String[] results = parameterMap.get(name);
                return results[0];
            }

            /**
             * 获取指定参数名的所有值的数组，如：checkbox的所有数据
             * 接收数组变量 ，如checkobx类型
             */
            @Override
            public String[] getParameterValues(String name) {
                return parameterMap.get(name);
            }

            @Override
            public Map<String, String[]> getParameterMap() {
                return parameterMap;
            }
        };
        return httpServletRequestWrapper;
    }

    private HttpServletRequestWrapper modifyBodyRequest(byte[] reqBodyBytes, HttpServletRequest request) {
        HttpServletRequestWrapper httpServletRequestWrapper = new HttpServletRequestWrapper(request) {
            @Override
            public ServletInputStream getInputStream() throws IOException {
                final ByteArrayInputStream bais = new ByteArrayInputStream(reqBodyBytes);
                return new ServletInputStream() {
                    // 修改body参数
                    @Override
                    public int read() throws IOException {
                        return bais.read();
                    }

                    @Override
                    public boolean isFinished() {
                        return false;
                    }

                    @Override
                    public boolean isReady() {
                        return true;
                    }

                    @Override
                    public void setReadListener(ReadListener listener) {
                    }
                    // 修改body参数结束
                };
            }

            @Override
            public BufferedReader getReader() throws IOException {
                return new BufferedReader(new InputStreamReader(getInputStream()));
            }
        };
        return httpServletRequestWrapper;
    }

    public static Map<String, String> coverMap(Map<String, Object> map) {
        Map<String, String> resmap = new HashMap<>();
        for (String m : map.keySet()) {
            Object param = map.get(m);
            String str = "";
            if (param != null && !param.toString().equals("")) {
                if (param instanceof Integer) {
                    str = param.toString();
                } else if (param instanceof String) {
                    str = param.toString();
                } else if (param instanceof Double) {
                    str = param.toString();
                } else if (param instanceof Float) {
                    str = param.toString();
                } else if (param instanceof BigDecimal) {
                    str = param.toString();
                } else if (param instanceof Long) {
                    str = param.toString();
                } else if (param instanceof Boolean) {
                    str = param.toString();
                } else if (param instanceof Date) {
                    str = JSONObject.toJSONStringWithDateFormat(param, "yyyy-MM-dd HH:mm:ss");
                } else {
                    str = JSON.toJSONString(param);
                }
            } else {
                str = "";
            }
            resmap.put(m, str);
        }
        return resmap;
    }

}
